#include <iostream>
#include <string>
#include "units.hpp"

namespace algorithms {

static void HandleParseStatus(ut_unit* unit, const std::string& str)
{
  if (unit == NULL)
  {
    ut_status status = ut_get_status();

    if (status == UT_BAD_ARG)
      throw units::Exception("empty units attribute string");

    if (status == UT_SYNTAX)
      throw units::Exception("'" + str + "' contained a syntax error");

    if (status == UT_UNKNOWN)
      throw units::Exception("'" + str + 
                             "' string contained an unknown identifier");

    throw units::Exception("Unhandled exception: " + status);
  }
}

static void HandleConverterStatus(cv_converter *converter,
                                  const std::string& from,
                                  const std::string& to)
{
  if (converter == NULL)
  {
    ut_status status = ut_get_status();

    if (status == UT_BAD_ARG)
      throw units::Exception("one of units is null");

    if (status == UT_NOT_SAME_SYSTEM)
      throw units::Exception("the units '" + from + "' and '" + to
                             + "' don't belong to the same unit-system");

    if (status == UT_MEANINGLESS)
      throw units::Exception("the units '" + from + "' and '" + to +
                             "' belong to the same unit-system but conversion "
                             + "between them is meaningless");

    throw units::Exception("Unhandled exception: " + status);
  }
}

static units::SmartUtSystem g_system;

static ut_status TimeBasic(const ut_unit* unit, void* arg)
{
  return static_cast<ut_status>(ut_are_convertible(unit, g_system.second()));
}

static ut_status TimeProduct(const ut_unit* unit, int count,
                             const ut_unit* const* basic_units,
                             const int* powers, void* arg)
{
  int result = 0;

  if (ut_are_convertible(unit, g_system.second()))
  {
    for (int i = 0; i < count; ++i)
    {
      if (ut_are_convertible(basic_units[i], g_system.second()) &&
          powers[i] == 1)
      {
        result = 1;
        break;
      }
    }
  }
  return static_cast<ut_status>(result);
}

static ut_status TimeGalilean(const ut_unit* unit, double scale,
                              const ut_unit* underlying_unit,
                              double origin, void* arg)
{
  return static_cast<ut_status>(0);
}

static ut_status TimeTimestamp(const ut_unit* unit, const ut_unit* timeUnit,
                               double origin, void* arg)
{
  return static_cast<ut_status>(1);
}

static ut_status TimeLogarithmic(const ut_unit* unit, double base,
                                 const ut_unit* reference, void* arg)
{
  return static_cast<ut_status>(0);
}

void Units::GetConverter(const std::string& from, const std::string& to,
                         double *offset, double *scale)
{
  if (from == to)
  {
    *scale = 1;
    *offset = 0;
  }
  else
  {
    ut_unit* ut_from = ut_parse(g_system.system(), from.c_str(), UT_UTF8);
    HandleParseStatus(ut_from, from);

    ut_unit* ut_to = ut_parse(g_system.system(), to.c_str(), UT_UTF8);
    HandleParseStatus(ut_from, to);

    cv_converter *converter = ut_get_converter(ut_from, ut_to);
    HandleConverterStatus(converter, from, to);

    *offset = cv_convert_double(converter, 0);
    *scale = cv_convert_double(converter, 1) - *offset;

    ut_free(ut_from);
    ut_free(ut_to);
    cv_free(converter);
  }
}

bool Units::AreConvertible(const std::string& unit1, const std::string& unit2)
{
  ut_unit* ut_unit1 = ut_parse(g_system.system(), unit1.c_str(), UT_UTF8);
  HandleParseStatus(ut_unit1, unit1);

  ut_unit* ut_unit2 = ut_parse(g_system.system(), unit2.c_str(), UT_UTF8);
  HandleParseStatus(ut_unit2, unit2);

  int result = ut_are_convertible(ut_unit1, ut_unit2);

  ut_free(ut_unit1);
  ut_free(ut_unit2);

  return result != 0;
}

bool Units::IsTime(const std::string& unit)
{
  ut_unit* ut_test = ut_parse(g_system.system(), unit.c_str(), UT_UTF8);
  HandleParseStatus(ut_test, unit);

  ut_visitor visitor;

  visitor.visit_basic       = TimeBasic;
  visitor.visit_product     = TimeProduct;
  visitor.visit_galilean    = TimeGalilean;
  visitor.visit_timestamp   = TimeTimestamp;
  visitor.visit_logarithmic = TimeLogarithmic;

  int result = ut_accept_visitor(ut_test, &visitor, NULL);

  ut_free(ut_test);

  return result;
}

}  // namespace algorithms
